﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace SQLPXBase
{
    public class ProxyManager<T>
        where T : SQLProxyBase
    {
        private readonly Dictionary<int, SqlProxyInstance> proxyPool;
        private SQLConnectionString connStr;

        #region SubClasses

        private class SqlProxyInstance
        {
            #region Eventi

            public event Handlers.OnDeadlockRetryHandler OnDeadlockRetry;
            //public delegate void OnDeadlockRetryHandler(ProxyCommand cmd, Exception ex);

            #endregion

            private T proxy;

            public DateTime CreatedOn { get; private set; }
            public DateTime LastUsed { get; private set; }

            public SqlProxyInstance(SQLPXBase.SQLConnectionString connStr)
            {
                proxy = (T) Activator.CreateInstance(typeof(T), connStr);
                proxy.Initialize(connStr);
                proxy.OnDeadlockRetry += proxy_OnDeadlockRetry;

                CreatedOn = DateTime.Now;
                LastUsed = DateTime.Now;
            }

            void proxy_OnDeadlockRetry(ProxyCommand cmd, Exception ex)
            {
                if (this.OnDeadlockRetry != null)
                    this.OnDeadlockRetry(cmd, ex);
            }

            public T GetProxy()
            {
                LastUsed = DateTime.Now;
                return proxy;
            }
        }
        public class SqlProxyInfo
        {
            public int ThrId { get; set; }
            public DateTime CreatedOn { get; set; }
            public DateTime LastUsed { get; set; }

            public SqlProxyInfo(int thrId, DateTime createdOn, DateTime lastUsed)
            {
                ThrId = thrId;
                CreatedOn = createdOn;
                LastUsed = lastUsed;
            }
        }

        #endregion

        #region Eventi

        public event Handlers.OnDeadlockRetryHandler OnDeadlockRetry;
        //public delegate void OnDeadlockRetryHandler(ProxyCommand cmd, Exception ex);

        #endregion

        private SQLProxyBase.ProxyAttributes attributes;

        private readonly object lockObject = new object();
        //private Dictionary<int, SqlProxyInstance> pool = new Dictionary<int, SqlProxyInstance>();

        public ProxyManager(SQLConnectionString connStr)
        {
            proxyPool = new Dictionary<int, SqlProxyInstance>();
            attributes = new SQLProxyBase.ProxyAttributes();
            this.connStr = connStr;
        }
        public ProxyManager(SQLConnectionString connStr, SQLProxyBase.ProxyAttributes attributes) 
            :this(connStr)
        {
            this.attributes = attributes;
        }

        public void Initialize(SQLConnectionString connStr, SQLProxyBase.ProxyAttributes attributes) 
        {
            this.connStr = connStr;
            this.attributes = attributes;
        }
        public void Initialize(SQLProxyBase.ProxyAttributes attributes)
        {
            this.attributes = attributes;
        }

        public T GetProxy()
        {
            lock (lockObject)
            {
                if (connStr == null)
                {
                    if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
                        throw new Exceptions.ProxyNotInitializedException();
                    return null;
                }

                Process thisProc = Process.GetCurrentProcess();
                ProcessThreadCollection myThreads = thisProc.Threads;
                int thrId = (int) AppDomain.GetCurrentThreadId();
                    //System.Threading.Thread.CurrentThread.ManagedThreadId;

                // Se il proxy c'è già, lo restituisce (non fa garbage collect)
                if (proxyPool.ContainsKey(thrId))
                {
                    proxyPool[thrId].GetProxy().Attributes = attributes;
                    return (T) proxyPool[thrId].GetProxy();
                }

                // Se viene richiesto un nuovo proxy, prima ottimizza i proxy esistenti
                List<int> garbage = new List<int>();

                // Controlla che tutti i proxy siano associati a thread ancora attivi
                foreach (int tId in proxyPool.Keys)
                {
                    bool found = myThreads.Cast<ProcessThread>().Any(t => t.Id == tId);
                    if (!found)
                        garbage.Add(tId);
                }

                // Ricicla un proxy, oppure ne crea uno nuovo
                SqlProxyInstance pi;
                if (garbage.Count > 0)
                    pi = proxyPool[garbage[0]];
                else
                {
                    pi = new SqlProxyInstance(connStr);
                    pi.GetProxy().Attributes = attributes;
                    pi.OnDeadlockRetry += pi_OnDeadlockRetry;
                }

                foreach (int i in garbage)
                    proxyPool.Remove(i);

                proxyPool.Add(thrId, pi);
                return pi.GetProxy();
            }
        }

        void pi_OnDeadlockRetry(ProxyCommand cmd, Exception ex)
        {
            if (OnDeadlockRetry != null)
                OnDeadlockRetry(cmd, ex);
        }

        public int CountPool
        {
            get { return proxyPool.Count; }
        }

        public void Purge(DateTime usedBefore)
        {
            lock (lockObject)
            {
                List<int> thrLst = new List<int>();
                foreach (KeyValuePair<int, SqlProxyInstance> pair in proxyPool)
                    if (pair.Value.LastUsed < usedBefore)
                        thrLst.Add(pair.Key);

                foreach (int thrId in thrLst)
                    if (proxyPool.ContainsKey(thrId))
                    {
                        proxyPool[thrId] = null;
                        proxyPool.Remove(thrId);
                    }
            }
        }

        public List<SqlProxyInfo> GetPoolInfo()
        {
            lock (lockObject)
            {
                List<SqlProxyInfo> res = new List<SqlProxyInfo>();
                foreach (KeyValuePair<int, SqlProxyInstance> pair in proxyPool)
                    res.Add(new SqlProxyInfo(pair.Key, pair.Value.CreatedOn, pair.Value.LastUsed));

                return res;
            }
        }

    }
}
